package promise

import (
	"fmt"
	actor2 "gitee.com/simplexyz/simplego/actor/define"
	"time"

	aactor "github.com/asynkron/protoactor-go/actor"

	sactor "gitee.com/simplexyz/simplego/actor"
)

const (
	defaultPromiseTimeoutDuration = 5 * time.Second
	maxPromiseTimeoutDuration     = 60 * time.Second
)

type Seq = uint64

type IPromiseTask interface {
	Promise() *Promise
	Next() IPromiseTask
	SetNext(next IPromiseTask)
}

type promiseTask struct {
	promise *Promise
	next    IPromiseTask
}

func newPromiseTask(promise *Promise) (t *promiseTask) {
	t = &promiseTask{
		promise: promise,
	}
	return
}

func (t *promiseTask) Promise() *Promise {
	return t.promise
}

func (t *promiseTask) Next() IPromiseTask {
	return t.next
}

func (t *promiseTask) SetNext(next IPromiseTask) {
	t.next = next
}

type rpcPromiseTask struct {
	*promiseTask
	iRequestMessage  nrpc.IMessage
	receiver         *aactor.PID
	iResponseMessage nrpc.IMessage
	callback         func()
}

func newRPCPromiseTask(promise *Promise, receiver *aactor.PID) (t *rpcPromiseTask) {
	t = &rpcPromiseTask{
		promiseTask: newPromiseTask(promise),
		receiver:    receiver,
	}
	return
}

func (t rpcPromiseTask) RequestMessage() nrpc.IMessage {
	return t.iRequestMessage
}

func (t rpcPromiseTask) Receiver() *aactor.PID {
	return t.receiver
}

func (t rpcPromiseTask) ResponseMessage() nrpc.IMessage {
	return t.iResponseMessage
}

// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type PromiseCallback = func(ctx *PromiseContext)
type PromiseOnCompleted = func(err error, ctx *PromiseContext)

type PromiseContext struct {
	seq              Seq
	actor            *actor2.Actor
	iRequestMessage  nrpc.IMessage
	receiver         *aactor.PID
	iResponseMessage nrpc.IMessage
	requested        bool
	err              error
}

func (p PromiseContext) Seq() Seq {
	return p.seq
}

func (p PromiseContext) RequestMessage() nrpc.IMessage {
	return p.iRequestMessage
}

func (p *PromiseContext) SetRequestMessage(iRequestMessage nrpc.IMessage) {
	p.iRequestMessage = iRequestMessage
}

func (p PromiseContext) Receiver() *aactor.PID {
	return p.receiver.Clone()
}

func (p *PromiseContext) SetReceiver(receiver *aactor.PID) {
	p.receiver = receiver.Clone()
}

func (p PromiseContext) ResponseMessage() nrpc.IMessage {
	return p.iResponseMessage
}

func (p *PromiseContext) SetResponseMessage(iResponseMessage nrpc.IMessage) {
	p.iResponseMessage = iResponseMessage
}

func (p PromiseContext) Requested() bool {
	return p.requested
}

func (p *PromiseContext) SetRequested(requested bool) {
	p.requested = requested
}

func (p *PromiseContext) Error() error {
	return p.err
}

func (p *PromiseContext) SetError(err error) {
	p.err = err
}

// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type Promise struct {
	*PromiseContext

	beginTime       time.Time
	timeoutDuration time.Duration
	timeout         time.Time

	cursor    int
	callbacks []PromiseCallback

	onCompleted PromiseOnCompleted
	completed   bool

	//curTask IPromiseTask
}

func (p *Promise) SetTimeout(timeout time.Duration) *Promise {
	p.timeoutDuration = timeout
	if p.timeoutDuration > maxPromiseTimeoutDuration {
		p.timeoutDuration = maxPromiseTimeoutDuration
	}
	p.timeout = p.beginTime.Add(p.timeoutDuration)
	return p
}

//func (p *Promise) ThenRPC(cb func()) *Promise {
//
//	return p
//}
//
//func (p *Promise) ThenTimeout() *Promise {
//	return p
//}
//
//func (p *Promise) ThenHttp() *Promise {
//	return p
//}

func (p *Promise) Then(cb PromiseCallback) *Promise {
	if cb == nil {
		return p
	}
	p.callbacks = append(p.callbacks, cb)
	return p
}

func (p *Promise) Add(child *Promise) *Promise {
	p.SetTimeout(p.timeoutDuration + child.timeoutDuration)

	if child.iRequestMessage != nil && !sactor.IsPIDEmpty(child.receiver) && !p.requested {
		p.callbacks = append(p.callbacks, func(context *PromiseContext) {
			context.iRequestMessage = child.iRequestMessage
			context.receiver = child.receiver
		})
	}

	p.callbacks = append(p.callbacks, child.callbacks...)
	return p
}

func (p *Promise) OnComplete(cb PromiseOnCompleted) *Promise {
	p.onCompleted = cb
	return p
}

func (p *Promise) Execute() *Promise {
	p.actor.addPromise(p)
	p.execute()
	return p
}

func (p *Promise) execute() {
	for p.cursor < len(p.callbacks) {
		if p.iRequestMessage != nil && !sactor.IsPIDEmpty(p.receiver) && !p.requested {
			if err := p.request(); err != nil {
				p.onComplete(err)
			}
			p.requested = true
			break
		}
		p.callbacks[p.cursor](p.PromiseContext)
		if p.PromiseContext.err != nil {
			p.onComplete(p.PromiseContext.err)
			return
		}
		p.cursor++
	}
	if p.cursor > len(p.callbacks)-1 {
		p.onComplete(nil)
	}
	return
}

func (p *Promise) request() (err error) {
	reqMsgData, err := p.iRequestMessage.Marshal()
	if err != nil {
		err = fmt.Errorf("marshal request message fail, message name=%s, %s", p.iRequestMessage.MessageName(), err)
		p.onComplete(err)
		return
	}

	p.beginTime = time.Now()
	p.timeout = p.beginTime.Add(p.timeoutDuration)

	send := Get_C2S_Promise()
	send.Seq = p.seq
	send.ReqMsgID = p.iRequestMessage.MessageID()
	send.ReqMsgData = reqMsgData
	sactor.RootContext().RequestWithCustomSender(p.receiver, send, p.actor.PID())
	return
}

func (p *Promise) onResponse(iResponseMessage nrpc.IMessage) (completed bool) {
	for p.cursor < len(p.callbacks) {
		if p.iRequestMessage != nil && !sactor.IsPIDEmpty(p.receiver) && !p.requested {
			if err := p.request(); err != nil {
				p.onComplete(err)
				return true
			}
			p.requested = true
			break
		}
		p.iResponseMessage = iResponseMessage
		p.requested = false
		p.callbacks[p.cursor](p.PromiseContext)
		if p.PromiseContext.err != nil {
			p.onComplete(p.PromiseContext.err)
			return true
		}
		p.cursor++
	}
	if p.cursor > len(p.callbacks)-1 {
		p.onComplete(nil)
		return true
	}
	return false
}

func (p *Promise) onComplete(err error) {
	p.onCompleted(err, p.PromiseContext)
	p.completed = true
}
